﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Web.Ajax.Data
{
	internal class ClauseTranslator : System.Linq.Expressions.ExpressionVisitor
	{
		StringBuilder sb=new StringBuilder();

		internal ClauseTranslator()
		{
		}

		internal static string Translate(Expression expression)
		{
			var t = new ClauseTranslator();
			t.Visit(expression);
			return t.sb.ToString();
		}

		private static Expression StripQuotes(Expression e)
		{
			while (e.NodeType == ExpressionType.Quote)
			{
				e = ((UnaryExpression)e).Operand;
			}
			return e;
		}		

		protected override Expression VisitMethodCall(MethodCallExpression m)
		{
			if (m.Method.DeclaringType == typeof(Queryable) && m.Method.Name == "Where")
			{
				sb.Append("SELECT * FROM (");
				this.Visit(m.Arguments[0]);
				sb.Append(") AS T WHERE ");
				LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
				this.Visit(lambda.Body);
				return m;
			}
			throw new NotSupportedException(string.Format("The method '{0}' is not supported", m.Method.Name));
		}

		protected override Expression VisitUnary(UnaryExpression u)
		{
			switch (u.NodeType)
			{
				case ExpressionType.Not:
					sb.Append(" NOT ");
					this.Visit(u.Operand);
					break;
				case ExpressionType.Convert:
					this.Visit(u.Operand);
					break;
				default:
					throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported", u.NodeType));
			}
			return u;
		}

		protected override Expression VisitBinary(BinaryExpression b)
		{
			sb.Append("(");
			this.Visit(b.Left);
			switch (b.NodeType)
			{
				case ExpressionType.And:
					sb.Append(" AND ");
					break;
				case ExpressionType.Or:
					sb.Append(" OR");
					break;
				case ExpressionType.Equal:
					sb.Append(" = ");
					break;
				case ExpressionType.NotEqual:
					sb.Append(" <> ");
					break;
				case ExpressionType.LessThan:
					sb.Append(" < ");
					break;
				case ExpressionType.LessThanOrEqual:
					sb.Append(" <= ");
					break;
				case ExpressionType.GreaterThan:
					sb.Append(" > ");
					break;
				case ExpressionType.GreaterThanOrEqual:
					sb.Append(" >= ");
					break;
				default:
					throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
			}
			this.Visit(b.Right);
			sb.Append(")");
			return b;
		}

		protected override Expression VisitConstant(ConstantExpression c)
		{
			IQueryable q = c.Value as IQueryable;
			if (q != null)
			{
				// assume constant nodes w/ IQueryables are table references
				sb.Append("SELECT * FROM ");
				sb.Append(q.ElementType.Name);
			}
			else 
			{
				var v = c.Value;
				if(c.Value!=null)
				{
					var vt=c.Value.GetType();
					if (vt.IsClass&&vt!=typeof(string)&&vt.BaseType==typeof(object))
					{
						var bt = vt.BaseType;
						var fl = vt.GetFields();
						if (fl.Length == 1)
							v = fl[0].GetValue(v);
					}
				}
				sb.Append(Sql.Convert(v));
			}
			return c;
		}

		protected override Expression VisitMember(MemberExpression m)
		{
			if (m.Expression != null)
			{
				if (m.Expression.NodeType == ExpressionType.Parameter)
				{
					sb.Append(Sql.ConvertCol(m.Member.Name));
					return m;
				}
			}
			return base.VisitMember(m);
		}
	}
}
